package com.wlyuan.open.application.aop.aspect;

import com.wlyuan.core.exception.ValueError;
import com.wlyuan.open.application.aop.annotation.Lock;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import lombok.var;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Pointcut;
import org.aspectj.lang.reflect.MethodSignature;
import org.redisson.api.RLock;
import org.redisson.api.RedissonClient;
import org.springframework.expression.ExpressionParser;
import org.springframework.expression.spel.standard.SpelExpressionParser;
import org.springframework.expression.spel.support.StandardEvaluationContext;
import org.springframework.stereotype.Component;

import java.lang.reflect.Method;
import java.lang.reflect.Parameter;
import java.util.concurrent.TimeUnit;

/**
 * @author lijing
 */
@Slf4j
@Aspect
@Component
@RequiredArgsConstructor
public class LockAspect {
    private final RedissonClient redissonClient;

    @Pointcut("@annotation(com.wlyuan.open.application.aop.annotation.Lock)")
    public void pointcut() {
    }

    @Around("pointcut()&&@annotation(annotation)")
    public Object around(ProceedingJoinPoint joinPoint, Lock annotation) throws Throwable {
        Object[] args = joinPoint.getArgs();
        RLock lock = redissonClient.getLock(this.lockKey(joinPoint, annotation.prefix(), annotation.value()));
        try {
            if (!lock.tryLock(annotation.ttl(), TimeUnit.SECONDS)) {
                throw new ValueError(annotation.name() + "进行中");
            }
            return joinPoint.proceed(args);
        } finally {
            if (lock.isLocked() && lock.isHeldByCurrentThread()) {
                lock.unlock();
            }
        }
    }

    private String lockKey(ProceedingJoinPoint joinPoint, String prefix, String keyExpression) {
        MethodSignature method = (MethodSignature) joinPoint.getSignature();
        // 解析el表达式，获取锁key
        var key = parseElExpression(joinPoint.getArgs(), method.getMethod(), keyExpression, String.class);
        return "PC::CACHE::OPEN::LOCK::" + prefix.toUpperCase() + ":" + key;
    }

    private static <T> T parseElExpression(Object[] args, Method method, String elExpression, Class<T> resultType) {
        Parameter[] parameters = method.getParameters();
        StandardEvaluationContext elContext = new StandardEvaluationContext();
        if (parameters != null && parameters.length > 0) {
            // 设置解析变量
            for (int i = 0; i < parameters.length; i++) {
                String paraName = parameters[i].getName();
                Object paraValue = args[i];
                elContext.setVariable(paraName, paraValue);
            }
        }
        ExpressionParser parser = new SpelExpressionParser();
        return parser.parseExpression(elExpression).getValue(elContext, resultType);
    }
}
